package com.xuecheng.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.base.execption.XueChengPlusException;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.base.model.RestResponse;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileService;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * @description TODO
 * @author Mr.M
 * @date 2022/9/10 8:58
 * @version 1.0
 */
@Slf4j
 @Service
public class MediaFileServiceImpl implements MediaFileService {
 @Autowired
 MinioClient minioClient;
  @Autowired
 MediaFilesMapper mediaFilesMapper;
 @Autowired
 MediaProcessMapper mediaProcessMapper;
 @Autowired
 MediaFileService currentProxy;
 //普通文件桶
 @Value("${minio.bucket.files}")
 private String bucket_Files;
 //视频桶
 @Value("${minio.bucket.videofiles}")
 private String bucket_videofiles;

 @Override
 public PageResult<MediaFiles> queryMediaFiels(Long companyId,PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {

  //构建查询条件对象
  LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();
  queryWrapper.eq(StringUtils.isNoneBlank(queryMediaParamsDto.getFileType()),MediaFiles::getFileType, queryMediaParamsDto.getFileType());
  queryWrapper.like(StringUtils.isNoneBlank(queryMediaParamsDto.getFilename()),MediaFiles::getFilename,queryMediaParamsDto.getFilename());
  queryWrapper.eq(StringUtils.isNoneBlank(queryMediaParamsDto.getAuditStatus()),MediaFiles::getAuditStatus,queryMediaParamsDto.getAuditStatus());
  //分页对象
  Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
  // 获取数据列表
  List<MediaFiles> list = pageResult.getRecords();
  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return mediaListResult;

 }

//  @Transactional
//  @Override
//  public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
//
//   //生成文件id，文件的md5值
//   String fileId = DigestUtils.md5Hex(bytes);
//   //文件名称
//   String filename = uploadFileParamsDto.getFilename();
//   //构造objectname
//   if (StringUtils.isEmpty(objectName)) {
//    objectName = fileId + filename.substring(filename.lastIndexOf("."));
//   }
//   if (StringUtils.isEmpty(folder)) {
//     //通过日期构造文件存储路径
//     folder = getFileFolder(new Date(), true, true, true);
//    } else if (folder.indexOf("/") < 0) {
//     folder = folder + "/";
//    }
//    //对象名称
//    objectName = folder + objectName;
////   MediaFiles mediaFiles = null;
//    try {
//     ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//     PutObjectArgs putObjectArgs = PutObjectArgs.builder()
//             .bucket(bucket_Files)
//             .object(objectName)
//             // -1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000
//             .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
//             .contentType(uploadFileParamsDto.getContentType())
//             .build();
////     上传到minio中
//     minioClient.putObject(putObjectArgs);
////     保存到数据库
//     MediaFiles files = mediaFilesMapper.selectById(fileId);
//     if (files==null) {
////      插入文件表
//      MediaFiles mediaFiles1 = new MediaFiles();
//      BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles1);
//      mediaFiles1.setId(fileId);
//      mediaFiles1.setFileId(fileId);
//      mediaFiles1.setCompanyId(companyId);
//      mediaFiles1.setFilename(filename);
//      mediaFiles1.setUrl("/" + bucket_Files + "/" + objectName);
//      mediaFiles1.setBucket(bucket_Files);
//      mediaFiles1.setFilePath(objectName);
//      mediaFiles1.setCreateDate(LocalDateTime.now());
//      mediaFiles1.setStatus("1");
//      mediaFiles1.setAuditStatus("002003");
//      //保存文件信息到文件表
//      int insert = mediaFilesMapper.insert(mediaFiles1);
//      if (insert < 0) {
//       XueChengPlusException.cast("保存文件信息失败");
//      }
//      UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
//      BeanUtils.copyProperties(mediaFiles1, uploadFileResultDto);
//      return uploadFileResultDto;
//     }
//    } catch (Exception e) {
//     log.debug("上传文件失败:{}",e.getMessage());
//     XueChengPlusException.cast("上传文件失败");
//    }
//   return null;
//  }


 @Override
 public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {

  //生成文件id，文件的md5值
  String fileId = DigestUtils.md5Hex(bytes);
  //文件名称
  String filename = uploadFileParamsDto.getFilename();
  //构造objectname
  if (StringUtils.isEmpty(objectName)) {
   objectName = fileId + filename.substring(filename.lastIndexOf("."));
  }
  if (StringUtils.isEmpty(folder)) {
   //通过日期构造文件存储路径
   folder = getFileFolder(new Date(), true, true, true);
  } else if (folder.indexOf("/") < 0) {
   folder = folder + "/";
  }
  //对象名称
  objectName = folder + objectName;
//   MediaFiles mediaFiles = null;
  try {
   addMediaFilesToMinIo(bytes,bucket_Files,objectName);
   MediaFiles mediaFiles1 = currentProxy.addMediaFilesToDb(fileId,bucket_Files, uploadFileParamsDto, companyId, objectName);
   UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
   assert mediaFiles1 != null;
   BeanUtils.copyProperties(mediaFiles1, uploadFileResultDto);
   return uploadFileResultDto;
 // }catch ()
 //  return null;
 //   return uploadFileResultDto;
 //   }
  } catch (Exception e) {
   log.debug("上传文件失败:{}",e.getMessage());
//   XueChengPlusException.cast("上传文件失败");
   throw new RuntimeException(e.getMessage());
  }
//  return null;
 }

 /**
  * @description 将文件写入minIO
  * @param bytes  文件字节数组
  * @param bucket  桶
  * @param objectName 对象名称

  * @return void
  * @author Mr.M
  * @date 2022/10/12 21:22
  */
//  将文件上传到分布式系统中
 private void addMediaFilesToMinIo(byte[] bytes,String bucket, String objectName){
//   资源的媒体类型
  String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;  // 默认未知二进制流
//  取objectName中的扩展名  c.jpg
  if (objectName.indexOf(".")>0) {
   String extension = objectName.substring(objectName.lastIndexOf("."));
   ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
   if (extensionMatch!=null) {
    contentType = extensionMatch.getMimeType();
   }
  }

  ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
   PutObjectArgs putObjectArgs = PutObjectArgs.builder()
           .bucket(bucket)
           .object(objectName)
           // -1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000
           .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
           .contentType(contentType)
           .build();
 //     上传到minio中
  try {
   minioClient.putObject(putObjectArgs);
  } catch (Exception e) {
   log.debug("上传文件失败：{}",e.getMessage());
   XueChengPlusException.cast("上传文件失败");
  }
 }

 /**
  * @description 将文件信息添加到文件表
  * @param fileId  文件md5值
  * @param bucket  桶
  * @param uploadFileParamsDto  上传文件的信息
  * @param companyId  机构id
  * @param objectName 对象名称
  * @return com.xuecheng.media.model.po.MediaFiles
  * @author Mr.M
  * @date 2022/10/12 21:22
  */
 @Transactional
 public MediaFiles  addMediaFilesToDb(String fileId, String bucket, UploadFileParamsDto uploadFileParamsDto, Long companyId, String objectName){
  //     保存到数据库
  MediaFiles files = mediaFilesMapper.selectById(fileId);
  if (files==null) {
//      插入文件表
   MediaFiles mediaFiles1 = new MediaFiles();
   BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles1);
   mediaFiles1.setId(fileId);
   mediaFiles1.setFileId(fileId);
   mediaFiles1.setCompanyId(companyId);
   mediaFiles1.setFilename(uploadFileParamsDto.getFilename());
//   获取扩展名
   String extension = null;
   String filename = uploadFileParamsDto.getFilename();
   if (StringUtils.isNoneBlank(filename) && filename.lastIndexOf(".")>=0) {
    extension = filename.substring(filename.indexOf("."));
   }
//   媒体类型
   String mimeTypeByextension = getMimeTypeByextension(extension);
   mediaFiles1.setBucket(bucket);
   mediaFiles1.setFilePath(objectName);
   mediaFiles1.setCreateDate(LocalDateTime.now());
   mediaFiles1.setStatus("1");
   mediaFiles1.setAuditStatus("002003");
//   图片  mp4 可以设置url
   if (mimeTypeByextension.indexOf("image")>=0 || mimeTypeByextension.indexOf("mp4")>=0) {
    mediaFiles1.setUrl("/" + bucket + "/" + objectName);
   }else {
//    处理视频
    MediaProcess mediaProcess = new MediaProcess();
    BeanUtils.copyProperties(mediaFiles1,mediaProcess);
    mediaProcess.setId(null);
    int insert = mediaProcessMapper.insert(mediaProcess);
    if (insert < 0) {
     XueChengPlusException.cast("插入文件处理失败");
    }
   }
//   mediaFiles1.setFileSize(UploadFileParamsDto);
//   mediaFiles1.setFileSize();
   //保存文件信息到文件表
   int insert = mediaFilesMapper.insert(mediaFiles1);
   if (insert < 0) {
    XueChengPlusException.cast("保存文件信息失败");
   }
   return mediaFiles1;
  } else {
    MediaFiles mediaFiles1 = new MediaFiles();
   BeanUtils.copyProperties(files, mediaFiles1);
   BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles1);
    mediaFiles1.setId(fileId);
    int i = mediaFilesMapper.updateById(mediaFiles1);
    if (i < 0) {
     XueChengPlusException.cast("保存文件信息失败");
   }
   return mediaFiles1;
  }
//  return null;
}



 //根据日期拼接目录
  private String getFileFolder(Date date, boolean year, boolean month, boolean day){
   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
   //获取当前日期字符串
   String dateString = sdf.format(new Date());
   //取出年、月、日
   String[] dateStringArray = dateString.split("-");
   StringBuffer folderString = new StringBuffer();
   if(year){
    folderString.append(dateStringArray[0]);
    folderString.append("/");
   }
   if(month){
    folderString.append(dateStringArray[1]);
    folderString.append("/");
   }
   if(day){
    folderString.append(dateStringArray[2]);
    folderString.append("/");
   }
   return folderString.toString();
  }



 @Override
 public RestResponse<Boolean> checkFile(String fileMd5) {
//  在文件表存在，并且在文件系统存在  此文件才存在
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  if (mediaFiles == null) {
   return RestResponse.success(false);
  }
//  是否在文件系统存在
  GetObjectArgs getObjectArgs = GetObjectArgs.
                                builder().
                                bucket(mediaFiles.getBucket()).
                                object(mediaFiles.getFilePath()).build();
  try {
   InputStream inputStream = minioClient.getObject(getObjectArgs);
   if (inputStream==null) {
    return RestResponse.success(false);
   }
  }catch (Exception e) {
   return RestResponse.success(false);
  }
  return RestResponse.success(true);
 }

 @Override
 public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
  //得到分块文件目录
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //得到分块文件的路径
  String chunkFilePath = chunkFileFolderPath + chunkIndex;

  //文件流
  InputStream fileInputStream = null;
  try {
   fileInputStream = minioClient.getObject(
           GetObjectArgs.builder()
                   .bucket(bucket_videofiles)
                   .object(chunkFilePath)
                   .build());

   if (fileInputStream != null) {
    //分块已存在
    return RestResponse.success(true);
   }
  } catch (Exception e) {
   return RestResponse.success(false);
  }
  //分块未存在
  return RestResponse.success(false);
 }

 //得到分块文件的目录
 private String getChunkFileFolderPath(String fileMd5) {
  return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
 }

 @Override
 public RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) {
  //得到分块文件的目录路径
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //得到分块文件的路径
  String chunkFilePath = chunkFileFolderPath + chunk;
  try {
   //将文件存储至minIO
   addMediaFilesToMinIo(bytes, bucket_videofiles,chunkFilePath);

  } catch (Exception ex) {
   ex.printStackTrace();
   XueChengPlusException.cast("上传过程出错请重试");
  }
  return RestResponse.success();
 }

 @Override
 public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
  String fileName = uploadFileParamsDto.getFilename();
  //  下载分块
  File[] chunkFiles  = checkChunkStatus(fileMd5, chunkTotal);
//  合并分块
  //扩展名
  String extName = fileName.substring(fileName.lastIndexOf("."));
  //创建临时文件作为合并文件
  File mergeFile = null;
  try {
   mergeFile = File.createTempFile(fileMd5, extName);
  } catch (IOException e) {
   XueChengPlusException.cast("合并文件过程中创建临时文件出错");
  }

  try {
   //开始合并
   byte[] b = new byte[1024];
   try(RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");) {
    for (File chunkFile : chunkFiles) {
     try (FileInputStream chunkFileStream = new FileInputStream(chunkFile);) {
      int len = -1;
      while ((len = chunkFileStream.read(b)) != -1) {
       //向合并后的文件写
       raf_write.write(b, 0, len);
      }
     }
    }
   } catch (IOException e) {
    e.printStackTrace();
    XueChengPlusException.cast("合并文件过程中出错");
   }
   log.debug("合并文件完成{}",mergeFile.getAbsolutePath());
   uploadFileParamsDto.setFileSize(mergeFile.length());

   try (InputStream mergeFileInputStream = new FileInputStream(mergeFile);) {
    //对文件进行校验，通过比较md5值
    String newFileMd5 = DigestUtils.md5Hex(mergeFileInputStream);
    if (!fileMd5.equalsIgnoreCase(newFileMd5)) {
     //校验失败
     XueChengPlusException.cast("合并文件校验失败");
    }
    log.debug("合并文件校验通过{}",mergeFile.getAbsolutePath());
   } catch (Exception e) {
    e.printStackTrace();
    //校验失败
    XueChengPlusException.cast("合并文件校验异常");
   }

   //将临时文件上传至minio
   String mergeFilePath = getFilePathByMd5(fileMd5, extName);
   try {

    //上传文件到minIO
    addMediaFilesToMinIo(mergeFile.getAbsolutePath(), bucket_videofiles, mergeFilePath);
    log.debug("合并文件上传MinIO完成{}",mergeFile.getAbsolutePath());
   } catch (Exception e) {
    e.printStackTrace();
    XueChengPlusException.cast("合并文件时上传文件出错");
   }

   //入数据库
   MediaFiles mediaFiles = addMediaFilesToDb(fileMd5,bucket_videofiles, uploadFileParamsDto, companyId,  mergeFilePath);
   if (mediaFiles == null) {
    XueChengPlusException.cast("媒资文件入库出错");
   }

   return RestResponse.success();
  } finally {
   //删除临时文件
   for (File file : chunkFiles) {
    try {
     file.delete();
    } catch (Exception e) {

    }
   }
   try {
    mergeFile.delete();
   } catch (Exception e) {

   }
  }
 }

 @Override
 public MediaFiles getFileById(String mediaId) {
  return mediaFilesMapper.selectById(mediaId);
 }

 private String getFilePathByMd5(String fileMd5,String fileExt){
  return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
 }

 //将文件上传到minIO，传入文件绝对路径
 public void addMediaFilesToMinIo(String filePath, String bucket, String objectName) {
  try {
   minioClient.uploadObject(
           UploadObjectArgs.builder()
                   .bucket(bucket)
                   .object(objectName)
                   .filename(filePath)
                   .build());
  } catch (Exception e) {
   e.printStackTrace();
   XueChengPlusException.cast("上传文件到文件系统出错");
  }
 }

 //检查所有分块是否上传完毕
 private File[] checkChunkStatus(String fileMd5, int chunkTotal) {
  //得到分块文件的目录路径
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  File[] files = new File[chunkTotal];
  //检查分块文件是否上传完毕
  for (int i = 0; i < chunkTotal; i++) {
   String chunkFilePath = chunkFileFolderPath + i;
   //下载文件
   File chunkFile =null;
   try {
    chunkFile = File.createTempFile("chunk" + i, null);
   } catch (IOException e) {
    e.printStackTrace();
    XueChengPlusException.cast("下载分块时创建临时文件出错");
   }
   downloadFileFromMinIO(chunkFile,bucket_videofiles,chunkFilePath);
   files[i]=chunkFile;
  }
  return files;
 }

 //根据桶和文件路径从minio下载文件
 public File downloadFileFromMinIO(File file,String bucket,String objectName){
  InputStream fileInputStream = null;
  OutputStream fileOutputStream = null;
  try {
   fileInputStream = minioClient.getObject(
           GetObjectArgs.builder()
                   .bucket(bucket)
                   .object(objectName)
                   .build());
   try {
    fileOutputStream = new FileOutputStream(file);
    IOUtils.copy(fileInputStream, fileOutputStream);

   } catch (IOException e) {
    XueChengPlusException.cast("下载文件"+objectName+"出错");
   }
  } catch (Exception e) {
   e.printStackTrace();
   XueChengPlusException.cast("文件不存在"+objectName);
  } finally {
   if (fileInputStream != null) {
    try {
     fileInputStream.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
   if (fileOutputStream != null) {
    try {
     fileOutputStream.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
  return file;
 }

// 根据扩展名匹配媒体类型
 private String getMimeTypeByextension(String extension){
 //   资源的媒体类型
   String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;  // 默认未知二进制流
 //  取objectName中的扩展名  c.jpg
    ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
    if (extensionMatch!=null) {
     contentType = extensionMatch.getMimeType();
    }
    return contentType;
 }
}
